package jnzb.control;

import java.io.File;
import java.io.IOException;

import jnzb.JNZB;
import jnzb.control.events.InfoEventProcessor;
import jnzb.control.events.InfoEventProcessorImpl;
import jnzb.control.loader.NZBStorage;
import jnzb.control.queue.change.QueueChangeProcessor;
import jnzb.control.queue.change.QueueDownChange;
import jnzb.control.queue.change.QueueRemoveChange;
import jnzb.control.queue.change.QueueUpChange;
import jnzb.control.uiUpdate.UIUpdateDelegate;
import jnzb.control.util.SimpleFileLogger;
import jnzb.monitor.DirectoryMonitor;
import jnzb.ui.UserInterface;
import nanoxml.XMLParseException;

/**
 * This is the controller for the GUI.
 * 
 * @author AA1
 * 
 */
public class Controller {

	// private UserInterface ui;

	private NZBProcessor nzbProcessor;

	private InfoEventProcessor infoProcessor = null;

	private UIUpdateDelegate uiDelegate;

	private DirectoryMonitor directoryMonitor = null;

	private RetryTimer retryTimer;

	/**
	 * This is the max time we'll wait for the other threads to exit gracefully
	 */
	public static final int EXIT_TIMEOUT = 30;

	/**
	 * Create an uninitialised controller. The controller needs the UI
	 * reference, which is set in the initialised() method. This must be called
	 * before you can use this class. It can't be passed in the constructor
	 * because the UI needs the controller in it's constructor!
	 */
	public Controller() {
	}

	/**
	 * Fired when the gui is closed so we can tidy up
	 */
	public synchronized void exitEvent() {
		this.nzbProcessor.stop();
		this.directoryMonitor.stop();
		infoProcessor.info("Exiting......");
		this.uiDelegate.hideGui();

		int attempts = 0;
		while (!nzbProcessor.isShutdown() && attempts < EXIT_TIMEOUT) {
			try {
				infoProcessor.debug("waiting");
				wait(1000);
				attempts++;
			} catch (InterruptedException e) {
				// Nothing to do here
			}
		}
		System.exit(0);
	}

	/**
	 * Received when a file is chosen as a result of clicking on the add nzb
	 * button.
	 * 
	 * The controller will not launch a thread to process this request, so Swing
	 * events calling this must do so
	 * 
	 * @param file
	 */
	public void loadNZBEvent(File file) {
		loadNZBEvent(file, true);
	}

	/**
	 * Received when a file is chosen as a result of clicking on the add nzb
	 * button.
	 * 
	 * The controller will not launch a thread to process this request, so Swing
	 * events calling this must do so
	 * 
	 * @param file
	 */
	public void loadNZBEvent(File file, boolean offerContinue) {
		// Open the file
		try {
			this.nzbProcessor.addNZB(NZBStorage.loadNZB(file, infoProcessor,
					offerContinue));
		} catch (XMLParseException e) {
			infoProcessor
					.error("The file does not seem to be a valid NZB file");
		} catch (IOException e) {
			infoProcessor.error("Error reading the file");
		}

	}

	/**
	 * Received when the start/pause button is pressed
	 */
	public void toggleStartEvent() {
		nzbProcessor.togglePaused();
		// Do this because the downloader will pause once a segment is
		// downloaded
		if (this.nzbProcessor.getDownloader().isPaused()) {
			infoProcessor.info("Pausing...");
		}
	}

	/**
	 * This method is a post-constructor constructor due to the
	 * inter-dependancies of the ui and controller
	 * 
	 * @param ui
	 * @param initialNZBFilename
	 *            - filename of an NZB to start processing (passed as a startup
	 *            parameter)
	 */
	public void initialise(UserInterface ui, String initialNZBFilename) {
		this.infoProcessor = new InfoEventProcessorImpl(ui, SimpleFileLogger.getInstace());
		this.nzbProcessor = new NZBProcessor(this, infoProcessor);
		this.uiDelegate = new UIUpdateDelegate(ui);
		this.retryTimer = new RetryTimer(this);

		uiDelegate.setWaitQueueModel(this.nzbProcessor.getQueueModel());
		uiDelegate.setFinishedModel(this.nzbProcessor.getFinishedModel());
		uiDelegate.setQueueManager(this.nzbProcessor.getQueueManager());

		// Create and kick off the diretory monitor
		this.directoryMonitor = new DirectoryMonitor(this, infoProcessor);
		new Thread(this.directoryMonitor).start();

		// Status messages
		infoProcessor.info("JNZB version " + JNZB.VERSION + " starting");
		Configuration config = Configuration.getInstance();
		if (config.ignoreParFiles()) {
			infoProcessor.info("PAR volume files will be ignored");
		}
		if (config.ignoreSampleFiles()) {
			infoProcessor.info("Sample files will be ignored");
		}

		// If there's an initial nzb
		if (initialNZBFilename != null
				&& initialNZBFilename.trim().length() > 0) {
			File initialNZB = new File(initialNZBFilename);
			// and it exists
			if (initialNZB.exists()) {
				// trigger the loading event - offer chance to continue
				this.loadNZBEvent(initialNZB, true);
			}

		}
	}

	/**
	 * @return true if the downloader is downloading
	 */
	public boolean isDownloading() {
		return this.nzbProcessor.getDownloader().isDownloading();
	}

	/**
	 * Returns the UI delegate object that is responsible for updating the gui
	 * 
	 * @return The UIDelegate implementation
	 */
	public UIUpdateDelegate getUiDelegate() {
		return this.uiDelegate;
	}

	/**
	 * Kick off the processing of a queue up event
	 * 
	 * @param selectedRow
	 */
	public void queueControllUpEvent(int selectedRow) {
		QueueChangeProcessor up = new QueueUpChange(nzbProcessor, uiDelegate,
				infoProcessor, selectedRow);
		Thread change = new Thread(up);
		change.start();
	}

	/**
	 * Kick off the processing of a queue down event
	 * 
	 * @param selectedRow
	 */
	public void queueControllDownEvent(int selectedRow) {
		QueueChangeProcessor down = new QueueDownChange(nzbProcessor,
				uiDelegate, infoProcessor, selectedRow);
		Thread change = new Thread(down);
		change.start();
	}

	/**
	 * Kick off the processing of a queue remove event
	 * 
	 * @param selectedRow
	 */
	public void queueControllRemoveEvent(int selectedRow) {
		QueueChangeProcessor remove = new QueueRemoveChange(nzbProcessor,
				uiDelegate, infoProcessor, selectedRow);
		Thread change = new Thread(remove);
		change.start();
	}

	/**
	 * @return the infoProcessor
	 */
	public InfoEventProcessor getInfoProcessor() {
		return infoProcessor;
	}

	/**
	 * Event fired by the auto-retry timer to automatically resume downloading
	 * after a fixed period of time
	 */
	public void autoRetryEvent() {
		// If we're not downloading already
		if (!isDownloading()){
			infoProcessor.info("Automatically retrying download");
			// TODO change to start and stop downloading methods
			nzbProcessor.togglePaused();
		}
	}
	
	/**
	 * Start the auto-retry timer
	 */
	public void startRetryTimer(){
		this.retryTimer.scheduleTimer();
	}

}
